/**
 *  Copyright 2011 Marco Berri - marcoberri@gmail.com
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 **/
package com;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Random;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
import com.utils.Default;
import com.utils.StringUtil;
import com.utils.VelocityUtil;

/**
 * Classe Base estesa dalle varie servlet
 *
 * @author Marco Berri marcoberri@gmail.com
 */
public class Base extends HttpServlet {

    /**
     * rimappatura della servlet application per storare dei dati
     */
    protected ServletContext application;
    /**
     * servlet configuration
     */
    protected ServletConfig config;
    /**
     * connessione al dbb
     */
    protected Connection conn_url;
    /**
     * connessione al db delle statistiche
     */
    protected Connection conn_stats;
    /**
     *
     */
    protected com.utils.VelocityUtil velocity;
    /**
     * logger
     */
    protected Logger log = null;
    /**
     * logger per il cron
     */
    protected Logger cron_log = null;
    /**
     * posizione del file di configurazione
     */
    protected String path_file_conf = "/WEB-INF/conf.xml";
    /**
     * Path della web-app
     */
    protected String path_app_root;
    /**
     * Configurazione
     */
    protected com.conf.Main conf;
    /**
     *
     */
    public static final String APP_ATT_APP_CONF = "app_conf";
    /**
     * lof della app
     */
    public static final String APP_ATT_CRON_LOG = "cron_log";
    /**
     * log del cron
     */
    public static final String APP_ATT_LOG = "log";
    /**
     * connessione al db principale
     */
    public static final String APP_ATT_CONN_URL = "conn_url";
    /**
     * connessione al db delle statistiche
     */
    public static final String APP_ATT_CONN_STATS = "conn_stats";

    /**
     *
     * @param config
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {

        this.config = config;
        super.init(config);
        this.application = config.getServletContext();
        this.path_app_root = application.getRealPath("/");

        velocity = new VelocityUtil();
        velocity.init(path_app_root);


        if (application.getAttribute(APP_ATT_APP_CONF) == null) {
            this.conf = new com.conf.Main(this.path_app_root + this.path_file_conf);
            application.setAttribute(APP_ATT_APP_CONF, this.conf);
        } else {
            this.conf = (com.conf.Main) application.getAttribute(APP_ATT_APP_CONF);
        }

        //verfico se è da ricaricare il file di conf
        if (this.conf.reload_if_changed(this.path_app_root + this.path_file_conf)) {
            application.setAttribute(APP_ATT_APP_CONF, this.conf);
        }



        try {
            if (application.getAttribute(APP_ATT_LOG) == null) {
                this.log = com.utils.Log4j.getLogger(conf.getLog().getFilename(), this.path_app_root + "/" + conf.getLog().getPath(), conf.getLog().getRolling());
                application.setAttribute(APP_ATT_LOG, this.log);
            } else {
                this.log = (Logger) application.getAttribute(APP_ATT_LOG);
            }

            if (application.getAttribute(APP_ATT_CRON_LOG) == null) {
                this.cron_log = com.utils.Log4j.getLogger(conf.getLogCron().getFilename(), this.path_app_root + "/" + conf.getLogCron().getPath(), conf.getLogCron().getRolling());
                application.setAttribute(APP_ATT_CRON_LOG, this.cron_log);
            } else {
                this.cron_log = (Logger) application.getAttribute(APP_ATT_CRON_LOG);
            }
        } catch (Exception e) {
            System.out.println("Problemi di configurazione per la parte dei log: " + e.getMessage());
        }
        debug("Conf:" + this.conf);

        if (application.getAttribute(APP_ATT_CONN_URL) != null) {
            this.conn_url = (Connection) application.getAttribute(APP_ATT_CONN_URL);
        }

        if (application.getAttribute(APP_ATT_CONN_STATS) != null) {
            this.conn_stats = (Connection) application.getAttribute(APP_ATT_CONN_STATS);
        }

        try {

            if (conn_url == null || conn_url.isClosed()) {
                Class.forName(conf.getDb().getClazz());
                conn_url = DriverManager.getConnection(conf.getDb().getConn());
                application.setAttribute(APP_ATT_CONN_URL, conn_url);
            }


            if (conn_stats == null || conn_stats.isClosed()) {
                Class.forName(conf.getStats().getClazz());
                conn_stats = DriverManager.getConnection(conf.getStats().getConn());
                application.setAttribute("conn_stats", conn_stats);
            }



        } catch (ClassNotFoundException ex) {
            fatal("ClassNotFoundException:" + ex, ex);

        } catch (SQLException ex) {
            fatal("SQLException:" + ex, ex);
        }


        try {

            for (String q : conf.getDb().getCreate()) {
                Statement stat = this.conn_url.createStatement();
                stat.executeUpdate(q);
                stat.close();
            }

            for (String q : conf.getStats().getCreate()) {
                Statement stat = this.conn_stats.createStatement();
                stat.executeUpdate(q);
                stat.close();
            }

        } catch (SQLException ex) {
            fatal("SQLException:" + ex, ex);
        }


        debug("end init - proc: " + Runtime.getRuntime().availableProcessors() + " - tot : " + bytesToMeg(Runtime.getRuntime().totalMemory()) + " - max : " + bytesToMeg(Runtime.getRuntime().maxMemory()) + " - free: " + bytesToMeg(Runtime.getRuntime().freeMemory()));

    }

    /**
     *
     */
    protected void close_connection() {

        debug("close_connection()");

        try {
            this.conn_url.close();
            this.conn_stats.close();
            this.application.removeAttribute("conn_url");
            this.application.removeAttribute("conn_stats");
            
        } catch (SQLException ex) {
            fatal("SQLException", ex);
        }


    }

    /**
     * destroy di tutte le servelt collegate
     */
    @Override
    public void destroy() {

        debug("destroy");
        this.close_connection();


        this.log.removeAllAppenders();
        this.log = null;
        application = null;
        config = null;
        conn_url = null;
        path_file_conf = null;
        path_app_root = null;
        conf = null;

    }

    /**
     *
     * @param text
     * @param e
     */
    protected void debug(String text, Exception e) {

        if (this.conf.getLog() == null || !this.conf.getLog().isDebug()) {
            return;
        }

        text = this.getClass().getName() + " - " + (bytesToMeg(Runtime.getRuntime().totalMemory()) - bytesToMeg(Runtime.getRuntime().freeMemory())) + "/" + bytesToMeg(Runtime.getRuntime().totalMemory()) + " - " + text;

        if (e == null) {
            this.log.debug(text);
        } else {
            this.log.debug(text, e);
        }
    }

    /**
     *
     * @param text
     */
    protected void debug(String text) {
        debug(text, null);
    }

    /**
     *
     * @param text
     * @param e
     */
    protected void fatal(String text, Exception e) {

        text = this.getClass().getName() + " - " + (bytesToMeg(Runtime.getRuntime().totalMemory()) - bytesToMeg(Runtime.getRuntime().freeMemory())) + "/" + bytesToMeg(Runtime.getRuntime().totalMemory()) + " - " + text;

        if (e == null) {
            this.log.fatal(text);
        } else {
            this.log.fatal(text, e);
        }
    }

    /**
     *
     * @param text
     */
    protected void fatal(String text) {
        fatal(text, null);
    }

    /**
     *
     * @param id
     * @return in base alla chiave richiesta ritorna l'url
     */
    protected String getUrlFromMap(String id) {

        HashMap<String, String> most_used_url;
        //verifico se esiste la mappa con gli ultimi url usati
        if (application.getAttribute("most_used_url") == null) {

            most_used_url = new HashMap<String, String>();
            application.setAttribute("most_used_url", most_used_url);
        }

        most_used_url = (HashMap<String, String>) application.getAttribute("most_used_url");

        return Default.toString(most_used_url.get(id), null);

    }

    /**
     *
     * @param id
     * @param url
     */
    @SuppressWarnings("unchecked")
    protected void putUrlIdToMap(String id, String url) {


        //devo verificare che la mappa non superi un tot di elementi ed eventualmente segare quelli aggiuntivi.

        HashMap<String, String> most_used_url;
        //verifico se esiste la mappa con gli ultimi url usati
        if (application.getAttribute("most_used_url") == null) {

            most_used_url = new HashMap<String, String>();
            application.setAttribute("most_used_url", most_used_url);
        }

        most_used_url = (HashMap<String, String>) application.getAttribute("most_used_url");

        if (most_used_url.size() > 1000) {
            most_used_url = new HashMap<String, String>();
        }

        most_used_url.put(id, url);

        application.setAttribute("most_used_url", most_used_url);


    }

    /**
     * Genera una stringa casuale di dimensione length
     *
     * @param length dinmensione della stringa casuale in caratteri
     * @return stringa casuale
     */
    protected static String randomString(int length) {
        Random rnd = new Random();
        char[] arr = new char[length];

        for (int i = 0; i < length; i++) {
            int n = rnd.nextInt(36);
            arr[i] = (char) (n < 10 ? '0' + n : 'a' + n - 10);
        }

        return new String(arr);
    }

    /**
     * salva le statistiche nel db delle statistiche per le visualizzazioni
     *
     * @param request la request attuale
     * @param url_id l'id dell'url
     */
    protected void saveStatsSave(HttpServletRequest request, String url_id) {
        saveStats("save", request, url_id);
    }

    /**
     * salva le statistiche nel db delle statistiche per le visualizzazioni
     *
     * @param request la request attuale
     * @param url_id l'id dell'url
     */
    protected void saveStatsView(HttpServletRequest request, String url_id) {
        saveStats("view", request, url_id);
    }

    /**
     * 
     * @param request
     * @param url_id
     */
    protected void saveStatsQrcode(HttpServletRequest request, String url_id) {
        saveStats("qrcode", request, url_id);
    }

    /**
     *
     * @param table
     * @param request
     * @param url_id
     */
    protected void saveStats(String table, HttpServletRequest request, String url_id) {
        Statement stat;
        try {
            stat = this.conn_stats.createStatement();

            String header = "";
            Enumeration headerNames = request.getHeaderNames();


            String X_Forwarded_For = null;
            while (headerNames.hasMoreElements()) {
                String headerName = (String) headerNames.nextElement();

                if (headerName.equals("X-Forwarded-For")) {
                    X_Forwarded_For = request.getHeader(headerName);

                }

                if (!StringUtil.isNullOrEmpty(header)) {
                    header += "||";
                }

                header += headerName + "|" + request.getHeader(headerName);

            }

            header += "||request_remote_addr" + "|" + request.getRemoteAddr();
            header += "||request_remote_host" + "|" + request.getRemoteHost();
            header += "||request_remote_user" + "|" + request.getRemoteUser();
            header += "||request_remote_remote_port" + "|" + request.getRemotePort();
            header += "||request_remote_url" + "|" + request.getRemotePort();

            //questo è da capire se ha senso
            String ip = request.getRemoteAddr();

            if (ip.equals("127.0.0.1") && X_Forwarded_For != null) {
                header += "||MBURL_request.getRemoteAddr()|" + ip;
                ip = X_Forwarded_For;
            }


            header = header.replaceAll("'", "\"");
            stat.executeUpdate("insert into " + table + " (fk_url_id,ip,header) values('" + url_id + "','" + ip + "','" + header + "')");
            stat.close();

        } catch (SQLException ex) {
            fatal("SQLException", ex);
        }

    }

    /**
     * verifica la presenza dell'ip nella tabella blacklist
     *
     * @param ip
     * @return boolean
     */
    protected boolean isBlackList(String ip) {

        if (getIPException(ip)) {
            return false;
        }

        try {
            Statement stat = this.conn_url.createStatement();
            ResultSet rs = stat.executeQuery("select * from blacklist where url='" + ip + "'");
            boolean is_bl = rs.next();
            rs.close();
            stat.close();
            return is_bl;
        } catch (SQLException ex) {
            fatal("SQLException", ex);
            return false;
        }


    }

    /**
     * Utility per la conversione da byte a Mega per la visualizzazione sul debug
     * @param bytes
     * @return long
     */
    public static long bytesToMeg(long bytes) {
        return bytes / (1024L * 1024L);
    }

    //da portare nelle conf oppure in un file a parte modificabile a mano.
    /**
     *
     * @param ip
     * @return boolean
     */
    protected boolean getIPException(String ip) {

        if (ip.equals("127.0.0.1")) {
            return true;
        }

        if (ip.equals("localhost")) {
            return true;
        }

        return false;
    }

    /**
     * Verifica che l'ip non sia nullo o presente nella blacklist
     *
     * @param ip
     * @return true|false
     */
    protected boolean isIpNullOrSpam(String ip) {

        if (com.utils.StringUtil.isNullOrEmpty(ip)) {
            fatal("tentativo di spam, remote address is null or Empty");
            return true;
        }


        //verifico che non sia nella blacklist
        if (isBlackList(ip)) {
            fatal("l'inidirizzo: " + ip + " è presente nella lista di spam");
            return true;
        }

        return false;


    }

    /**
     *
     * @param k
     * @return
     */
    protected boolean isValidHeaderKey(String k) {

        HashMap<String, String> key_not_valid = new HashMap<String, String>();
        key_not_valid.put("Host", "not valid");
        key_not_valid.put("Connection", "not valid");
        key_not_valid.put("X-Forwarded-Server", "not valid");
        key_not_valid.put("X-Forwarded-Host", "not valid");
        key_not_valid.put("X-Forwarded-For", "not valid");
        key_not_valid.put("request_remote_addr", "not valid");
        key_not_valid.put("request_remote_host", "not valid");
        key_not_valid.put("request_remote_user", "not valid");
        key_not_valid.put("Via", "not valid");
        key_not_valid.put("MBURL_request.getRemoteAddr()", "not valid");


        return (key_not_valid.get(k) == null);
    }
}
